package cs236369.webserver.queue.managers;

import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URLDecoder;
import java.net.URL;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.HttpServerConnection;
import org.apache.http.HttpStatus;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpDateGenerator;
import org.apache.http.protocol.HttpRequestHandler;
import org.apache.http.protocol.UriPatternMatcher;

import cs236369.webserver.DTOs.MimeMapping;
import cs236369.webserver.DTOs.Request;
import cs236369.webserver.DTOs.ServerConfigDTO;
import cs236369.webserver.dynamicLoad.HandlerLoader;
import cs236369.webserver.queue.handlers.Worker;

public class RequestTaskManager extends TaskManager<Request> {
	
	private final UriPatternMatcher requestHandlerRegistry;
	private final ServerConfigDTO serverConfig;
	private final Collection<MimeMapping> mimes;

	public RequestTaskManager(UriPatternMatcher requestHandlerRegistry, ServerConfigDTO serverConfig, Collection<MimeMapping> mimes) {
		super(serverConfig.getNumOfReqHandlerThreads());
		this.requestHandlerRegistry = requestHandlerRegistry;
		this.serverConfig = serverConfig;
		this.mimes = mimes;
	}

	@Override
	protected Thread newWorker(ConcurrentLinkedQueue<Request> taskQueue) {
		return new RequestHandler(taskQueue);
	}
	
	class RequestHandler extends Worker<Request> {

		public RequestHandler(ConcurrentLinkedQueue<Request> queue) {
			super(queue);
		}

		@Override
		protected void handleTask(Request objectToHandle) {
			HttpServerConnection conn = objectToHandle.getConnection();
			HttpRequest req = objectToHandle.getRequest();
			HttpResponse hrsp = new BasicHttpResponse(new ProtocolVersion("HTTP",1,1), 200, "");
			String path = getPathAndParams(req, null);
			File f;
			if(path.equals("/") || path.equals("")) {
				f = new File(serverConfig.getBaseDir(),"/"+serverConfig.getWelcomeFilename());
				BasicHttpEntityEnclosingRequest newReq = 
					new BasicHttpEntityEnclosingRequest(req.getRequestLine().getMethod()
													   ,"/"+serverConfig.getWelcomeFilename()
													   ,req.getRequestLine().getProtocolVersion()
													   );
				for(Header h : req.getAllHeaders()) {
					newReq.addHeader(h);
				}
				if(req instanceof HttpEntityEnclosingRequest) {
					newReq.setEntity(((HttpEntityEnclosingRequest)req).getEntity());
				}
				req = newReq;
			} else {
				f = new File(serverConfig.getBaseDir(),path);
			}
			try { //1
				if(!f.getCanonicalPath().startsWith(serverConfig.getBaseDir())) {
					throw new SecurityException();
				}
			} catch (IOException e) { //1
				sendError(conn, HttpStatus.SC_INTERNAL_SERVER_ERROR, "Access Error");
				e.printStackTrace();
				return;
			} catch (SecurityException e) { //1
				sendError(conn, HttpStatus.SC_FORBIDDEN, "Not Allowed");
				e.printStackTrace();
				return;
			}
			
			HandlerLoader hl = null;
			synchronized(requestHandlerRegistry) {
				hl = (HandlerLoader) requestHandlerRegistry.lookup(path);
			}
			
			try {
				HttpRequestHandler hrh;
				synchronized (hl) {
					hrh = hl.newInstance();
				}
				hrh.handle(req, hrsp, setContext());
			} catch (Exception e) {
					sendError(conn,HttpStatus.SC_INTERNAL_SERVER_ERROR, "Our Problem");
					e.printStackTrace();
					return;
			} 
			
			checkAndFix(hrsp);
			
			try {
				conn.sendResponseHeader(hrsp);
				conn.sendResponseEntity(hrsp);
				conn.flush();
				conn.close();
			} catch (HttpException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		private HttpContext setContext() {
			BasicHttpContext ret = new BasicHttpContext();
			ret.setAttribute("baseDir", serverConfig.getBaseDir());
			ret.setAttribute("mimes", mimes);
			return ret;
		}

		private void checkAndFix(HttpResponse hrsp) {
			if(hrsp.getStatusLine() == null) {
				hrsp.setStatusLine(new ProtocolVersion("HTTP",1,1), HttpStatus.SC_OK, "OK");
			}
			if(!hrsp.containsHeader(HTTP.DATE_HEADER)) {
				HttpDateGenerator dgen = new HttpDateGenerator();
				hrsp.setHeader(HTTP.DATE_HEADER,dgen.getCurrentDate());
			}
			if(!hrsp.containsHeader(HTTP.CONTENT_TYPE)) {
				HttpEntity ent = hrsp.getEntity();
				if(!(ent == null)) {
					hrsp.setHeader(ent.getContentType());
				}
			}
			if(!hrsp.containsHeader(HTTP.CONTENT_LEN)) {
				HttpEntity ent = hrsp.getEntity();
				if(ent == null){
					hrsp.setHeader(HTTP.CONTENT_LEN, "0");
				} else {
					hrsp.setHeader(HTTP.CONTENT_LEN,Long.toString(ent.getContentLength()));
				}
			}
			hrsp.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
		}

		private void sendError(HttpServerConnection conn, int statusCode, String reason) {
			HttpResponse hrsp = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), statusCode, reason);
			HttpDateGenerator dgen = new HttpDateGenerator();
			hrsp.setHeader(HTTP.CONTENT_LEN, "0");
			hrsp.setHeader(HTTP.DATE_HEADER,dgen.getCurrentDate());
			hrsp.setHeader(HTTP.CONN_DIRECTIVE, HTTP.CONN_CLOSE);
			try { //2
				conn.sendResponseHeader(hrsp);
				conn.flush();
				conn.close();
			} catch (Exception ei) { //2
				ei.printStackTrace();
			}
		}

		private String getPathAndParams(HttpRequest req, Map<String,String> m) {
			String query = null;
			String path = null;
			try {
				// for parsing's sake, assume it's an absolute URL
				URL url = new URL(req.getRequestLine().getUri());
				query = url.getQuery();
				path = url.getPath();
			} catch(MalformedURLException e) {
				// so it was a relative URL, no biggy
				String[] sp = req.getRequestLine().getUri().split("\\?");
				path = sp[0];
				if(sp.length > 1)
					query = sp[1];
				query = null;
			}
			
			queryIntoMap(query, m);
			return path;
		}

		private void queryIntoMap(String query, Map<String, String> m) {
			if(query != null && m != null)
				for(String param : query.split("&")) {
					String[] p = param.split("=");
					try {
						String key = URLDecoder.decode(p[0], "UTF-8");
						String value = URLDecoder.decode(p[1], "UTF-8");
						m.put(key, value);
					} catch(UnsupportedEncodingException e) {
						//TODO Tough Luck
					}
				}
		}
		
	}

}
